#ifndef _DISTANCES_H_
#define _DISTANCES_H_

#include <vector>
#include <complex>
#include <algorithm>
#include <functional>
#include <numeric>
#include <iostream>
#include "complex_ops.h"
#include "criterion.h"
#include "NumericTypes.h"

struct MinDistance
{
    const char* getName()
    {
        const static char name[] = "MinDistance"; 
        return name;
    }
    
    template
    <
        typename SignalSystem
    >
    double operator()(SignalSystem& sys)
    {
        using namespace std;
        
        if(sys.empty())
            return 0;
        
        double minDistantion = 100000000;
        double maxDistantion = 0;
        
        for (int i = 0; i < sys.size(); ++ i)
        {
            for (int j = i + 1; j < sys.size(); ++ j)
            {
                minDistantion = min(minDistantion, MinDistance::dist(sys[i], sys[j]));
                maxDistantion = max(maxDistantion, MinDistance::dist(sys[i], sys[j]));
            }
        }
    
        return 1.0 - minDistantion / maxDistantion;
    }
    
private:
    
    static double dist(
        std::vector<std::complex<double> > x,
        std::vector<std::complex<double> > y
    )
    {
        using namespace std;
        
        if(x.size() > y.size())
            swap(x, y);
        
        auto diff = y;
        
        transform(
            x.begin(), x.end(),
            diff.begin(), diff.begin(),
            minus<complex<double> >()
        );
        
        vector<double> rslt(diff.size());

        transform(
            diff.begin(), diff.end(),
            rslt.begin(),
            absolute<complex<double>,double>()
        );

        std::binder2nd
        <
            power<int, double>
        >
        power2(
            power<int, double>(),
            2
        );
        
        transform(
            rslt.begin(), rslt.end(),
            rslt.begin(),
            power2
        );
      
        double sum =
            accumulate(
                rslt.begin(), rslt.end(),
                0.0
            );
        
        return sqrt(sum);
    }
};

struct AvrDistance
{
    const char* getName()
    {
        const static char name[] = "AvrDistance"; 
        return name;
    }
    
    template
    <
        typename SignalSystem
    >
    double operator()(SignalSystem& sys)
    {
        using namespace std;
        
        if(sys.empty())
            return 0;
        
        double avrDistantion = 0;
        double maxDistantion = 0;
        unsigned int count = 0;
        for (int i = 0; i < sys.size(); ++ i)
        {
            for (int j = i + 1; j < sys.size(); ++ j)
            {
                ++ count;
                avrDistantion += AvrDistance::dist(sys[i], sys[j]);
                maxDistantion = max(maxDistantion, AvrDistance::dist(sys[i], sys[j]));
            }
        }
    
        avrDistantion /= (double)count;
        return 1.0 - (avrDistantion / maxDistantion); 
    }
    
private:
    
    static double dist(
        std::vector<std::complex<double> > x,
        std::vector<std::complex<double> > y
    )
    {
        using namespace std;
        
        if(x.size() > y.size())
            swap(x, y);
        
        auto diff = y;
        
        transform(
            x.begin(), x.end(),
            diff.begin(), diff.begin(),
            minus<complex<double> >()
        );
        
        vector<double> rslt(diff.size());

        transform(
            diff.begin(), diff.end(),
            rslt.begin(),
            absolute<complex<double>,double>()
        );

        std::binder2nd
        <
            power<int, double>
        >
        power2(
            power<int, double>(),
            2
        );
        
        transform(
            rslt.begin(), rslt.end(),
            rslt.begin(),
            power2
        );
      
        double sum =
            accumulate(
                rslt.begin(), rslt.end(),
                0.0
            );
        
        return sqrt(sum);
    }
};

#endif // _BER_H_
